package cn.enjoy.design.behavior.strategy.springInitializingBeanPlus;


import cn.enjoy.design.behavior.strategy.springInitializingBean.service.ShopRankHandler;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 利用Spring框架中的InitializingBean接口与ApplicationContextAware接口来实现策略beans的自动装配。
 * InitializingBean接口中的afterPropertiesSet()方法在类的实例化过程当中执行，当客户端完成注入ShopRankHandlerFactory工厂类实例的时候，
 * afterPropertiesSet()也已经执行完成。因此可以通过重写afterPropertiesSet()方法，
 * 在其中利用getBeansOfType()方法来获取到策略接口的所有实现类，并存于Map容器之中，达到工厂类与具体的策略类解耦的目的。
 * <p>
 * 参考：https://mp.weixin.qq.com/s/Ovoy1CnN92xCYHbbO4H_DQ
 *
 * @author K
 * @date 2022-11-01 20:12
 */
public class HandlerFactory<E,T extends GenericInterface<E>> implements InitializingBean, ApplicationContextAware {

    private ApplicationContext applicationContext;

    private final Class<T> strategyInterfaceType;

    /**
     * 策略实例容器
     */
    private Map<E, T> STRATEGY_MAP;

    /**
     * java泛型只存在于编译期，无法通过例如T.class的方式在运行时获取其类信息
     * 因此利用构造函数传入具体的策略类型class对象为getBeansOfType()方法
     * 提供参数
     *
     * @param strategyInterfaceType 要传入的策略接口类型
     */
    public HandlerFactory(Class<T> strategyInterfaceType) {
        this.strategyInterfaceType = strategyInterfaceType;
    }

    /**
     * 根据不同参数类型获取对应的接口实现类
     *
     * @param type 参数类型
     * @return 参数类型对应的接口实现类
     */
    public GenericInterface<E> getStrategy(E type) {
        return STRATEGY_MAP.get(type);
    }

    @Override
    public void afterPropertiesSet() {
        Map<String, T> beansOfType = applicationContext.getBeansOfType(strategyInterfaceType);

        STRATEGY_MAP = Optional.ofNullable(beansOfType)
                .map(beansOfTypeMap -> beansOfTypeMap.values().stream()
                        .filter(handleInterface -> StringUtils.isNotEmpty(handleInterface.getType().toString()))
                        .collect(Collectors.toMap(GenericInterface::getType, Function.identity())))
                .orElse(new HashMap<>(8));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}